/*
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package hextd.core;

import hextd.UI.PanelStatusMultiPlayer;
import hextd.UI.PanelStatusSinglePlayer;
import hextd.creep.Creep;
import hextd.tower.Tower;
import hextd.tower.TowerFactory.type;
import hextd.wave.Path;
import java.text.DecimalFormat;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import javax.swing.border.TitledBorder;

/**
 *
 * @author hylke
 */
public class ContextPlayer {

	private String playerName;
	private int userId = -1;
	private int lives = 20;
	private long credits = 0;
	private long payQueue = 0;
	//
	private int interestStart = 3;
	private int interestIncrease = 3;
	private int bonus = 0;
	private int bonusWait = 0;
	private int payQueueBonus = 0;
	private long score = 0;
	//
	private ArrayList<Tower> towers;
	private int[] towerCounts;
	private Context context;
	private ArrayList<Creep> targets;
	private ArrayList<Path> paths;
	private int lastTick = 0;
	private boolean active = false;
	//
	private PanelStatusMultiPlayer psmp;
	private PanelStatusSinglePlayer pssp;
	private String waveString = "";
	private static DecimalFormat form = new DecimalFormat("#,###.##");

	public ContextPlayer(Context c) {
		towers = new ArrayList<Tower>();
		towerCounts = new int[type.values().length];
		targets = new ArrayList<Creep>();
		paths = new ArrayList<Path>();
		context = c;
	}

	private void livesChanged() {
		if (this.pssp != null) {
			this.pssp.setLives("" + getLives() / 10f);
		}
		if (this.psmp != null) {
			this.psmp.setLives("" + getLives() / 10f);
		}
		context.fireLivesChangedEvent(this);
	}

	private void moneyChanged() {
		if (this.pssp != null) {
			this.pssp.setCredits("€" + form.format(getCredits(true)) + "");
		}
		if (this.psmp != null) {
			this.psmp.setCredits("€" + form.format(getCredits(true)) + "");
		}
		context.fireMoneyChangedEvent(this);
	}

	private void bonusChanged() {
		if (this.pssp != null) {
			this.pssp.setBonus("" + getBonus(true));
		}
		if (this.psmp != null) {
			this.psmp.setBonus("" + getBonus(true));
		}
		interestChanged();
		context.fireMoneyChangedEvent(this);
	}

	private void scoreChanged() {
		if (this.pssp != null) {
			this.pssp.setScore("" + form.format(getScore()));
		}
		if (this.psmp != null) {
			this.psmp.setIncome("" + form.format(getScore()));
		}
	}

	private void interestChanged() {
		if (pssp != null) {
			this.pssp.setInterest("" + getInterest() + "%");
		}
		context.fireMoneyChangedEvent(this);
	}

	private void nameChanged() {
		TitledBorder b;
		if (this.pssp != null) {
			this.pssp.setCredits("€" + form.format(getCredits(true)) + "");
			b = (TitledBorder) this.pssp.getBorder();
			b.setTitle(playerName);
		}
		if (this.psmp != null) {
			//this.psmp.setCredits("€" + form.format(getCredits(true)) + "");
			psmp.setTitle(playerName);
		}
	}

	public PanelStatusSinglePlayer getStatusPanelSinglePlayer() {
		if (this.pssp == null && context instanceof ContextNormal) {
			this.pssp = new PanelStatusSinglePlayer((ContextNormal) context);
			this.pssp.setInterest("" + getInterest() + "%");
			this.pssp.setScore("" + form.format(getScore()));
			this.pssp.setBonus("" + getBonus(true));
			this.pssp.setCredits("€" + form.format(getCredits(true)) + "");
			this.pssp.setLives("" + getLives() / 10f);
			this.pssp.setWave(waveString);
		}
		return pssp;
	}

	public PanelStatusMultiPlayer getStatusPanelMultiPlayer() {
		if (this.psmp == null && context instanceof ContextNormal) {
			this.psmp = new PanelStatusMultiPlayer((ContextNormal) context);
			this.psmp.setBonus("" + getBonus(true));
			this.psmp.setCredits("€" + form.format(getCredits(true)) + "");
			this.psmp.setLives("" + getLives() / 10f);
		}
		return psmp;
	}

	public void waveChanged(String wave) {
		if (this.pssp != null) {
			this.pssp.setWave(wave);
			this.waveString = wave;
		}
	}

	public synchronized void payInterest() {
		if (active) {
			long interest = (long) (credits * (interestStart + bonus * interestIncrease) / 100l);
			bonus += this.bonusWait;
			bonusWait = 0;
			credits += interest;
			score += interest;
			scoreChanged();
			moneyChanged();
			interestChanged();
		}
	}

	public void addScore(int s) {
		if (this.active && s >= 0) {
			this.score += s;
			this.scoreChanged();
		}
	}

	public void deductScore(int s) {
		if (this.active) {
			if (s < 0) {
				System.out.println("ContextPlayer::deductScore: Deducting negative score? " + s + "");
			}
			this.score -= s;
			this.scoreChanged();
		}
	}

	/**
	 * Check if the user can pay the amount. checkQueue indicates wether the amount
	 * of scheduled payments should be taken into account too. Normally you want to
	 * do this!
	 * @param amount The amount to check for (0 to check bonus)
	 * @param checkQueue Should the paybent queue be taken into account? This should almost always be true!
	 * @return Wether the player can pay the amount
	 */
	public synchronized boolean canPay(long amount, boolean checkQueue) {
		//System.out.println("Context::canPay: checking "+amount);
		if (active) {
			if (checkQueue) {
				if (amount > 0) {
					return (credits - payQueue >= amount);
				} else {
					return (bonus - payQueueBonus + bonusWait > 0);
				}
			} else {
				if (amount > 0) {
					return (credits >= amount);
				} else {
					return (bonus + bonusWait > 0);
				}
			}
		}
		return false;
	}

	/**
	 * Queue a payment.
	 * @param amount Amount to queue (0 to queue bonus)
	 */
	public synchronized boolean queuePayment(long amount) {
		if (active) {
			if (canPay(amount, true)) {
				if (amount == 0) {
					payQueueBonus++;
				} else if (amount > 0) {
					payQueue += amount;
				} else {
					System.out.println("ContextPlayer::queuePayment: queueing negative amount: " + amount);
				}
				moneyChanged();
				return true;
			}
		} else {
			System.out.println("ContextPlayer::not queuing for inactive player.");
		}
		return false;
	}

	/**
	 * UnQueue a payment.
	 * @param amount Amount to unqueue (0 to unqueue bonus)
	 */
	public synchronized void unQueuePayment(long amount) {
		if (active) {
			if (amount == 0) {
				payQueueBonus--;
			} else if (amount > 0) {
				payQueue -= amount;
			} else {
				System.out.println("ContextPlayer::unQueuePayment: unqueueing negative amount: " + amount);
			}
			moneyChanged();
		} else {
			System.out.println("ContextPlayer::unQueuePayment: not unqueuing for inactive player.");
		}
	}

	/**
	 * Make the player pay amount. If the payment was queued, deduct it from the queue too.
	 * Note: Mixing queued and unqueued payments is a bad idea!
	 * @param amount Amount to pay.
	 * @param wasQueued Was the amount queued previously?
	 * @return Wether the payment succeeded.
	 */
	public synchronized boolean doPay(long amount) {
		if (this.active) {
			if (payQueue > 0 || payQueueBonus > 0) {
				if (amount > 0) {
					if (this.payQueue < amount) {
						System.err.println("ContextPlayer::doPay: ERROR! paying more then was queued!" +
								" Queue=" + this.payQueue +
								" amount=" + amount +
								" queue=" + payQueue + " " + payQueueBonus);
						return false;
					}
					if (this.credits < amount) {
						System.err.println("ContextPlayer::doPay: ERROR! Not enough money to pay queue!" +
								" credits=" + this.credits +
								" amount=" + amount +
								" queue=" + payQueue + " " + payQueueBonus);
						this.payQueue -= amount;
						return false;
					}
					this.payQueue -= amount;
					this.credits -= amount;
					this.moneyChanged();
					return true;
				} else {
					if (this.payQueueBonus <= 0) {
						System.err.println("ContextPlayer::doPay: ERROR! paying more bonus then was queued!");
						return false;
					}
					if (this.bonus + this.bonusWait <= 0) {
						System.err.println("ContextPlayer::doPay: ERROR! Not enough bonus to pay queue!");
						return false;
					}
					this.payQueueBonus--;
					if (this.bonusWait > 0) {
						this.bonusWait--;
					} else {
						this.bonus--;
					}
					this.bonusChanged();
					return true;
				}
			} else {
				if (amount > 0) {
					if (this.canPay(amount, false)) {
						this.credits -= amount;
						//System.out.println("Context::doPay: credits: "+this.credits);
						this.moneyChanged();
						return true;
					} else {
						return false;
					}
				} else {
					if (this.canPay(amount, false)) {
						if (this.bonusWait > 0) {
							this.bonusWait--;
						} else {
							this.bonus--;
						}
						this.bonusChanged();
						return true;
					} else {
						return false;
					}
				}
			}
		}
		return false;
	}

	/**
	 * Pays amount credits to this player. If amount == 0, then pays 1 bonus point.
	 * @param amount Amount to pay to the player.
	 */
	public synchronized void doReceive(long amount) {
		if (this.active) {
			if (amount > 0) {
				this.credits += amount;
				this.moneyChanged();
			} else if (amount == 0) {
				this.bonusWait++;
				this.bonusChanged();
			}
		}
	}

	public void addTower(Tower t) {
		towers.add(t);
		towerCounts[t.getType().ordinal()]++;
	}

	public void removeTower(Tower t) {
		towers.remove(t);
		towerCounts[t.getType().ordinal()]--;
	}

	public ArrayList<Tower> getTowers() {
		return towers;
	}

	public int getTowerCount(type type) {
		return towerCounts[type.ordinal()];
	}

	public void addPath(Path p) {
		this.paths.add(p);
	}

	public Collection<Path> getPaths() {
		return this.paths;
	}

	/**
	 * Removes a life from the player.
	 * @param player
	 * @return true if there are more lives left.
	 */
	public boolean removeLife(int count) {
		if (active) {
			lives -= count;
			//System.out.println("ContextPlayer::removeLife: Player: " + userId + " Lives: " + lives);
			if (lives <= 0) {
				context.playerDied(this);
			}
			livesChanged();
			return (lives > 0);
		}
		return false;
	}

	public void addBonus() {
		if (this.active) {
			this.bonus++;
			this.bonusChanged();
		}
	}

	public int getInterest() {
		return interestStart + this.bonus * this.interestIncrease;
	}

	public int getBonus(boolean deductPayQueue) {
		if (deductPayQueue) {
			return bonus + bonusWait - payQueueBonus;
		} else {
			return bonus + bonusWait;
		}
	}

	public void addTarget(Creep e) {
		targets.add(e);
	}

	public void removeTarget(Creep e) {
		targets.remove(e);
	}

	public void removeAllTargets() {
		targets.clear();
	}

	public List<Creep> getTargets() {
		return targets;
	}

	//
	// Standard getters and setters;
	//
	public int getLives() {
		return lives;
	}

	public void setLives(int lives) {
		this.lives = lives * 10;
		this.livesChanged();
	}

	public long getCredits(boolean deductPayQueue) {
		if (deductPayQueue) {
			return credits - payQueue;
		} else {
			return credits;
		}
	}

	public void resetCredits(long credits, int bonus) {
		this.credits = credits + payQueue;
		this.bonus = bonus + payQueueBonus;
		bonusWait = 0;
		moneyChanged();
	}

	public int getInterestStart() {
		return interestStart;
	}

	public void setInterestStart(int interestStart) {
		this.interestStart = interestStart;
		this.interestChanged();
	}

	public int getInterestIncrease() {
		return interestIncrease;
	}

	public void setInterestIncrease(int interestIncrease) {
		this.interestIncrease = interestIncrease;
	}

	public int getBonusWait() {
		return bonusWait;
	}

	public void setBonusWait(int bonusWait) {
		this.bonusWait = bonusWait;
		this.bonusChanged();
	}

	public long getScore() {
		return score;
	}

	public void setScore(long score) {
		this.score = score;
		this.scoreChanged();
	}

	public int getLastTick() {
		return lastTick;
	}

	public void setLastTick(int lastTick) {
		this.lastTick = lastTick;
	}

	public boolean isActive() {
		return active;
	}

	public void setActive(boolean active) {
		this.active = active;
	}

	/**
	 * @return the playerName
	 */
	public String getPlayerName() {
		return playerName;
	}

	/**
	 * @param playerName the playerName to set
	 */
	public void setPlayerName(String playerName) {
		this.playerName = playerName;
		this.nameChanged();
	}

	public int getUserId() {
		return userId;
	}

	public void setUserId(int userId) {
		this.userId = userId;
	}
}
